package org.toves.checklua.warnings;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.toves.checklua.ast.AstNode;
import org.toves.checklua.ast.AstProcessor;
import org.toves.checklua.ast.AstProcessorResult;
import org.toves.checklua.ast.Complaint;
import org.toves.checklua.scan.Token;

public class UnknownReadProcessor implements AstProcessor {
	private static final String LUA_BUILTINS = ("assert dofile error _G getfenv"
			+ " getmetatable ipairs load loadfile loadstring next pairs pcall"
			+ " print rawequal rawget rawset select setfenv setmetatable"
			+ " tonumber tostring type unpack _VERSION xpcall"
			+ " coroutine module require package string table math io file os"
			+ " debug nxt");

	private HashSet<String> globals;
	
	public UnknownReadProcessor() {
		globals = new HashSet<String>();
		for (String s : LUA_BUILTINS.split(" ")) {
			globals.add(s);
		}
	}
	
	public void reset() {
		globals.clear();
	}
	
	public AstProcessorResult process(AstNode root) {
		UnknownReadResult result = new UnknownReadResult(this);
		addGlobalDefinitions(root, result);
		findInvalidUses(root, result);
		return result;
	}
	
	private void addGlobalDefinitions(AstNode n, UnknownReadResult result) {
		boolean stmt = n.isStatement();
		if (stmt) {
			result.pushLevel();
		}
		if (n.getNodeType() == AstNode.NAME) {
			Token token = n.getRepresentativeToken();
			if (n.isDefineGlobal()) {
				result.addGlobal(token.getText());
			} else if (n.isDefineLocal()) {
				result.addLocal(token.getText());
			}
		}
		for (AstNode child : n.getChildren()) {
			addGlobalDefinitions(child, result);
		}
		if (stmt) {
			result.popLevel();
		}
	}
	
	private void findInvalidUses(AstNode n, UnknownReadResult result) {
		boolean stmt = n.isStatement();
		if (stmt) {
			result.pushLevel();
		}
		if (n.getNodeType() == AstNode.NAME) {
			if (n.isDefineLocal()) {
				result.addLocal(n.getRepresentativeToken().getText());
			} else if (n.isRead()) {
				Token nameToken = n.getRepresentativeToken();
				String name = nameToken.getText();
				if (!result.contains(name)){
					String msg = "unknown name";
					String[] suggest = result.findClosestMatch(name);
					if (suggest != null) {
						String alt = suggest[0];
						String diff = suggest[1];
						msg += " - maybe you meant " + alt;
						if (diff.length() <= 30) {
							msg += " (" + diff + ")";
						} else if (alt.toUpperCase().equals(name.toUpperCase())) {
							msg += " (watch capitalization)";
						}
					}
					result.addComplaint(Complaint.createWarning(nameToken, msg));
				}
			}
		}
		for (AstNode child : n.getChildren()) {
			findInvalidUses(child, result);
		}
		if (stmt) {
			result.popLevel();
		}
	}
	
	
	public boolean containsGlobal(String symbol) {
		return globals.contains(symbol);
	}
	
	public boolean addGlobal(String symbol) {
		return globals.add(symbol);
	}
	
	public Set<String> getGlobals() {
		return Collections.unmodifiableSet(globals);
	}
}
